﻿using System;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Linq;
using System.Windows.Threading;
using LockCrypt.Core;
using LockCrypt.Properties;
using NDesk.Options;
using LockCrypt.DAL;
using System.IO;

namespace LockCrypt {
    public partial class App : Application {
        public App() {
            this.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);
            FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(System.Windows.Markup.XmlLanguage.GetLanguage(System.Globalization.CultureInfo.CurrentCulture.IetfLanguageTag)));
            WPFConfigurationManager.Configure();
            try {
                var klingon = System.Globalization.CultureInfo.GetCultureInfo("en-GB-IK");
            } catch {
                string cultureCode = "en-GB-IK";
                CultureAndRegionInfoBuilder builder = new CultureAndRegionInfoBuilder(cultureCode, CultureAndRegionModifiers.None);
                builder.LoadDataFromCultureInfo(CultureInfo.GetCultureInfo("en-GB"));
                builder.LoadDataFromRegionInfo(new RegionInfo("en-GB"));
                builder.TwoLetterISOLanguageName = cultureCode.Substring(0, 2);
                builder.TwoLetterISORegionName = "unknown";
                builder.CultureNativeName = "Klingon";
                builder.CultureEnglishName = "Klingon";
                builder.Register();
            }
        }

        private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) {
            WinUnhandledException dlg = new WinUnhandledException(e.Exception);
            dlg.ShowDialog();
        }

        protected override void OnStartup(StartupEventArgs e) {
            base.OnStartup(e);
            WinMain mainWindow;
            if(e.Args != null && e.Args.Length > 0) {
                string dbPath = null,
                       dbHost = null,
                       dbUser = null,
                       dbPass = null,
                       dbDatabase = null,
                       dbPrefix = string.Empty,
                       dbMode = null,
                       encryption = null,
                       password = null;
                int dbPort = -1;
                bool showHelp = false;
                OptionSet opts = new OptionSet() {
                    { "db:", "The database path", (string path) => dbPath = path },
                    { "dbhost:", "The database hostname", (host) => dbHost = host },
                    { "dbport:", "The database port number", (int port) => dbPort = port },
                    { "dbuser:", "The database username", (user) => dbUser = user },
                    { "dbpass:", "The database password", (pass) => dbPass = pass },
                    { "dbdatabase:", "The database name", (db) => dbDatabase = db },
                    { "dbprefix:", "The database table prefix", (prefix) => dbPrefix = prefix },
                    { "m|mode:", "The database connection type. Either "+ Utils.Implode(Enum.GetNames(typeof(Constants.DatabaseType)), ", ", false, "or"), (string mode) => dbMode = mode },
                    { "e|encryption:", "The encryption algorithm. Either "+ Utils.Implode(Enum.GetNames(typeof(EncryptionProviders)), ", ", false, "or"), (string algorithm) => encryption = algorithm },
                    { "pw:", "The password", (string pass) => password = pass },
                    { "h|?|help", v=> showHelp = true },
                };
                opts.Parse(e.Args);
                if(showHelp == true) {
                    Console.WriteLine("Check the help file or http://wiki.lime49.com for more information");
                    opts.WriteOptionDescriptions(Console.Out);
                    Shutdown();
                } else {
                    if(dbPath == null && e.Args.Length == 1) {
                        // only one argument and it's not to show help
                        string descriptorFilepath = e.Args[0];
                        if(!File.Exists(descriptorFilepath)) {
                            Console.WriteLine("Descriptor file '{0}' does not exist", descriptorFilepath);
                            Shutdown();
                        }
                        try {
                            string[] descriptorLines = File.ReadAllLines(descriptorFilepath, Encoding.UTF8);
                            if(descriptorLines.Length == 1 || !(descriptorLines.FirstOrDefault() ?? string.Empty).ToLowerInvariant().Contains("lockcrypt db")) {
                                Console.WriteLine("Invalid LockCrypt descriptor");
                                Shutdown();
                            }
                            opts.Parse(descriptorLines.Skip(1));
                            if(!string.IsNullOrEmpty(dbPath) && !dbPath.Contains(Path.AltDirectorySeparatorChar) && !dbPath.Contains(Path.DirectorySeparatorChar)) {
                                // only a filename specified, look in the directory of the descriptor and lockcrypt directories
                                try {
                                    string descriptorDirectory = Path.GetDirectoryName(descriptorFilepath);
                                    string dbInDescriptorDir = Path.Combine(descriptorDirectory, dbPath),
                                           dbInAppDir = Path.Combine(Utils.GetApplicationPath(), dbPath);
                                    if(File.Exists(dbInDescriptorDir)) {
                                        dbPath = dbInDescriptorDir;
                                    } else if(File.Exists(dbInAppDir)) {
                                        dbPath = dbInAppDir;
                                    }
                                } catch {
                                    throw new ArgumentException("Invalid characters in descriptor path or database path");
                                }
                            }
                        } catch(Exception ex) {
                            Console.WriteLine("Error parsing descriptor file: {0}", ex.Message);
                        }
                    }
                    NetworkDbCredentials dbCredentials = null;
                    if(!string.IsNullOrEmpty(dbHost) &&
                       !string.IsNullOrEmpty(dbUser) &&
                       !string.IsNullOrEmpty(dbDatabase)) {
                        dbCredentials = new NetworkDbCredentials(dbHost, dbPort, dbUser, dbPass, dbDatabase, dbPrefix);
                    }
                    InitFromArgs(dbPath, dbCredentials, dbMode, encryption, password);
                }
            } else {
                mainWindow = new WinMain();
                mainWindow.ShowDialog();
            }
            Shutdown();
        }

        /*// <summary>
        /// Initializes from a descriptor.
        /// </summary>
        private void InitFromDescriptor(DatabaseDescriptor descriptor) {
            WinMain mainWindow;
            IDbProvider provider = null;
            if(descriptor.DatabaseCredentials != null) {

            } else if(!string.IsNullOrEmpty(descriptor.DatabasePath)) {
                if(!File.Exists(descriptor.DatabasePath)) {
                    Console.WriteLine("Database '{0}' does not exist", descriptor.DatabasePath);
                } else {

                }
            } else {
                Console.WriteLine("Database not specified");
                return;
            }

            
            if(!File.Exists(descriptor.DatabasePath)) {
                Console.WriteLine("Database '{0}' does not exist", descriptor.DatabasePath);
            } else {
                bool valid = false;
                if(dbMode == null) {
                    // try to guess filetype
                    Console.WriteLine("File type not specified, attempting to detect");
                    try {
                        ILogProvider logger = new ConsoleLogger();
                        IReadOnlyDatabase db = AutomaticParser.GetDatabaseProvider(dbPath, logger);
                        if(!(db is IDbProvider)) {
                            throw new LockCryptException("Filetype not supported", 230);
                        }
                        provider = (IDbProvider)db;
                        valid = true;
                    } catch(Exception ex) {
                        Console.WriteLine("Error detecting filetype: {0}", ex.Message);
                    }
                } else {
                    Constants.DatabaseType dbType;
                    EncryptionProviders algorithm;
                    try {
                        dbType = (Constants.DatabaseType)Enum.Parse(typeof(Constants.DatabaseType), dbMode, true);
                        if(string.IsNullOrEmpty(encryption)) {
                            algorithm = EncryptionProviders.None;
                            Console.WriteLine("No encryption algorithm specified, assuming none.");
                        } else {
                            algorithm = (EncryptionProviders)Enum.Parse(typeof(EncryptionProviders), encryption, true);
                        }
                        provider = DalConfigurationManager.GetProvider(dbType, algorithm, password, dbPath);
                        valid = true;
                    } catch {
                        Console.WriteLine("Invalid database mode or encryption");
                    }
                }
                if(valid) {
                    DalConfigurationManager.Provider = provider;
                    mainWindow = new WinMain();
                    mainWindow.ShowDialog();
                }
            }
        }*/

        /// <summary>
        /// Initializes from command line arguments.
        /// </summary>
        /// <param name="dbPath">The database path.</param>
        /// <param name="dbMode">The database mode.</param>
        /// <param name="encryption">The encryption algorithm.</param>
        /// <param name="password">The encryption password.</param>
        //private void InitFromArgs(string dbPath, string dbMode, string encryption, string password) {
        private void InitFromArgs(string dbPath, NetworkDbCredentials dbCredentials, string dbMode, string encryption, string password) {
            IDbProvider provider = null;
            bool valid = false;

            if(!string.IsNullOrEmpty(dbPath)) {
                if(!File.Exists(dbPath)) {
                    Console.WriteLine("Database '{0}' does not exist", dbPath);
                } else {
                    if(dbMode == null) {
                        // try to guess filetype
                        Console.WriteLine("File type not specified, attempting to detect");
                        try {
                            ILogProvider logger = new ConsoleLogger();
                            IReadOnlyDatabase db = AutomaticParser.GetDatabaseProvider(dbPath, logger);
                            if(!(db is IDbProvider)) {
                                throw new LockCryptException("Filetype not supported", 230);
                            }
                            provider = (IDbProvider) db;
                            valid = true;
                        } catch(Exception ex) {
                            Console.WriteLine("Error detecting filetype: {0}", ex.Message);
                        }
                    } else {
                        Constants.DatabaseType dbType;
                        EncryptionProviders algorithm;
                        try {
                            dbType = (Constants.DatabaseType) Enum.Parse(typeof(Constants.DatabaseType), dbMode, true);
                            if(string.IsNullOrEmpty(encryption)) {
                                algorithm = EncryptionProviders.None;
                                Console.WriteLine("No encryption algorithm specified, assuming none.");
                            } else {
                                algorithm = (EncryptionProviders) Enum.Parse(typeof(EncryptionProviders), encryption, true);
                            }
                            provider = DalConfigurationManager.GetProvider(dbType, algorithm, password, dbPath);
                            valid = true;
                        } catch {
                            Console.WriteLine("Invalid database mode or encryption");
                        }
                    }
                }
            } else if(dbCredentials != null) {
                Constants.DatabaseType dbType;
                EncryptionProviders algorithm;
                try {
                    dbType = (Constants.DatabaseType) Enum.Parse(typeof(Constants.DatabaseType), dbMode, true);
                    if(string.IsNullOrEmpty(encryption)) {
                        algorithm = EncryptionProviders.None;
                        Console.WriteLine("No encryption algorithm specified, assuming none.");
                    } else {
                        algorithm = (EncryptionProviders) Enum.Parse(typeof(EncryptionProviders), encryption, true);
                    }
                    provider = DalConfigurationManager.GetProvider(dbType, algorithm, password, dbCredentials);
                    valid = true;
                } catch {
                    Console.WriteLine("Invalid database mode or encryption");
                }
            } else {
                Console.WriteLine("Database not specified");
                return;
            }
            if(valid) {
                DalConfigurationManager.Provider = provider;
                WinMain mainWindow = new WinMain();
                mainWindow.ShowDialog();
            }
        }
    }
}